classdef PowerController < handle
    %POWERCONTROLLER Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        session
        device
        zi
        
        osc_ch
        dmd_ch
        ctl_ch
        pid_ch
        aux_ch
        sig_ch
        
        dmod_bw
        dmod_od
        trans_rt

        last_power_calibration
    end
    
    methods
        function obj = PowerController(session, dev, varargin)
            %POWERCONTROLLER Construct an instance of this class
            %   Detailed explanation goes here
            p=inputParser;
            
            isSession = @(x) isa(x, 'MeasurementSession.AbstractMeasurementSession');
            osc_chTest = @(x) ismember(x, {'0', '1'});
            dmd_chTest = @(x) ismember(x, {'0', '1'});
            ctl_chTest = @(x) ismember(x, {'0', '1'});
            pid_chTest = @(x) ismember(x, {'0', '1', '2', '3'});
            aux_chTest = @(x) ismember(x, {'0', '1', '2', '3'});
            sig_chTest = @(x) ismember(x, {'0', '1'});
            isnonneg = @(x) x > 0;
            orderTest = @(x) ismember(x, 1:8);
            
            p.addRequired('session', isSession);
            p.addRequired('dev', @ischar);
            p.addParameter('osc_channel', '0', osc_chTest);
            p.addParameter('dmd_channel', '0', dmd_chTest);
            p.addParameter('ctl_channel', '0', ctl_chTest);
            p.addParameter('pid_channel', '0', pid_chTest);
            p.addParameter('aux_channel', '0', aux_chTest);
            p.addParameter('sig_channel', '0', sig_chTest);
            p.addParameter('dmod_bandwidth', 10e3, isnonneg);
            p.addParameter('dmod_order', 4, orderTest);
            p.addParameter('tranfer_rate', 10e3, isnonneg);
            p.parse(session, dev, varargin{:});
            
            obj.session = p.Results.session;
            obj.device = p.Results.dev;
            obj.osc_ch = p.Results.osc_channel;
            obj.dmd_ch = p.Results.dmd_channel;
            obj.ctl_ch = p.Results.ctl_channel;
            obj.pid_ch = p.Results.pid_channel;
            obj.aux_ch = p.Results.aux_channel;
            obj.sig_ch = p.Results.sig_channel;
            obj.dmod_bw = p.Results.dmod_bandwidth;
            obj.dmod_od = p.Results.dmod_order;
            obj.trans_rt = p.Results.tranfer_rate;
            
            obj.zi = obj.session.getDevice(obj.device);
            obj.initialize();
        end
        
        function initialize(obj)
            obj.setSigIn().setOsc_Dmod().setContrlVoltage(0);
            try
                obj.load_calibration();
                pd_volt = obj.last_power_calibration.pdVolt2Power.volt;
                power = obj.last_power_calibration.pdVolt2Power.power;
                obj.fit_calibration(pd_volt, power);
            catch
                warning('No calibration data found. Need Calibration.')
            end
        end
        
        %% PID Control
        function obj = lock(obj)
            ziDAQ('setInt', ['/' obj.device '/pids/' obj.pid_ch '/enable'], 1); pause(0.1);
        end
        function obj = unlock(obj)
            ziDAQ('setInt', ['/' obj.device '/pids/' obj.pid_ch '/enable'], 0); pause(0.1);
        end
        
        %% Get Power
        function [pwr, uncertainty ] = getPower(obj, duration)
            if nargin == 1
                duration = 1.0;
            end            
            [volt, sigma_v] = obj.getPDVoltage(duration);
            p1 = obj.last_power_calibration.calibration_fit.p1; p2 = obj.last_power_calibration.calibration_fit.p2;
            pwr = p1*volt + p2; 
            uncertainty = p1*sigma_v;
        end
        
        function [volt, sigma_v] = getPDVoltage(obj, duration)
            if nargin == 1
                duration = 1.0;
            end
            demod_r = obj.plotter_dmod([], 'duration', duration);
            volt = mean(demod_r.getNodeData('0', 'r').value);
            sigma_v = std(demod_r.getNodeData('0', 'r').value);
        end
        %% Target Setting
        function obj = setTargetPower(obj, power)
            p1 = obj.last_power_calibration.calibration_fit.p1; p2 = obj.last_power_calibration.calibration_fit.p2;
            volt = (power - p2)/p1;
            obj.setTarget(volt);
        end
        
        function obj = setTarget(obj, pd_volt)
            ctl_volt = obj.find_control_volt(pd_volt);
            obj.setContrlVoltage(ctl_volt);
            obj.setPID(pd_volt).lock().enableControlOutput();
        end

        function ctl_volt=find_control_volt(obj, pd_volt)
            list = find(obj.last_power_calibration.ctrlVolt2pdVolt.voltPD < pd_volt);
            ctl_volt = obj.last_power_calibration.ctrlVolt2pdVolt.voltCtrl(length(list));
        end
        
        %% Power Controller Settings
        function obj = enableControlOutput(obj)
            ziDAQ('setDouble', ['/' obj.device '/sigouts/' obj.ctl_ch '/range'], 10);
            obj.zi.fireSigOut();
        end
        function obj = disableControlOutput(obj)
            obj.zi.silenceSigOut();
        end
        
        function obj=setSigIn(obj)
            ziDAQ('setInt', ['/' obj.device '/sigins/' obj.sig_ch '/ac'], 0);
            ziDAQ('setInt', ['/' obj.device '/sigins/' obj.sig_ch '/imp50'], 0);
            ziDAQ('setInt', ['/' obj.device '/sigins/' obj.sig_ch '/diff'], 0);
            ziDAQ('setDouble',['/' obj.device '/sigins/' obj.sig_ch '/range'], 2.0);
        end
        function obj=setOsc_Dmod(obj)
            obj.zi.disableOutAmplitude('*');
            obj.zi.setOscFreq(obj.osc_ch, 0.0);
            obj.zi.setDemodFilterOrder(obj.dmd_ch, obj.dmod_od).setDemodFilterBW(obj.dmd_ch, obj.dmod_bw);
            obj.zi.setDataTranferRate(obj.dmd_ch, obj.trans_rt).enableDataTransfer(obj.dmd_ch);
        end
        
        function obj=setModulation(obj, varargin)
            p=inputParser;
            p.addParameter('osc_ch', '0');
            p.addParameter('mod_freq', 100);
            p.addParameter('mod_amp', 0.1);
            p.parse(varargin{:});
            
            obj.disableModulation('osc_ch', p.Results.osc_ch);
            
            out_range = ziDAQ('getDouble', ['/' obj.device '/sigouts/' obj.ctl_ch '/range']);
            ziDAQ('setDouble', ['/' obj.device '/oscs/'   p.Results.osc_ch '/freq'],  p.Results.mod_freq);
            ziDAQ('setInt',    ['/' obj.device '/demods/' p.Results.osc_ch '/oscselect'],  str2double(p.Results.osc_ch) );
            ziDAQ('setDouble', ['/' obj.device '/sigouts/' obj.sig_ch '/amplitudes/' p.Results.osc_ch ],  p.Results.mod_amp/out_range);
        end
        function obj=enableModulation(obj, varargin)
            p=inputParser;
            p.addParameter('osc_ch', '1');
            p.parse(varargin{:});
            ziDAQ('setInt', ['/' obj.device '/sigouts/' obj.sig_ch '/enables/' p.Results.osc_ch ], 1);
        end
        function obj=disableModulation(obj, varargin)
            p=inputParser;
%             p.addParameter('osc_ch', '1');
            p.addParameter('osc_ch', '0');
            p.parse(varargin{:});
            ziDAQ('setInt', ['/' obj.device '/sigouts/' obj.sig_ch '/enables/' p.Results.osc_ch ], 0);
        end
        
        function obj=setContrlVoltage(obj, volt, varargin)
            p=inputParser;
            voltTest = @(x) x>=-10.0 && x <= 10.0;
            p.addRequired('volt', voltTest);
%             p.addParameter('aux_voltage', 0.0, voltTest);
            p.parse(volt, varargin{:});
            
            obj.zi.setSigOutOffset(volt);            

%             v_aux = p.Results.aux_voltage;
%             v_out = (p.Results.volt - v_aux)/10.0;
%             obj.zi.setSigOutOffset(v_out);
%             obj.zi.setAuxOutSelect(obj.aux_ch, -1).setAuxOutOffset(obj.aux_ch, v_aux);
        end
        
        function obj = setPID(obj, set_point, varargin)
            p=inputParser;
            isnonneg = @(x) x > 0;
            p.addRequired('set_point', isnonneg);
            p.addParameter('limit_range', 2.0, isnonneg);
            p.addParameter('p', 3.018);
            p.addParameter('i', 122.3e3);
            p.addParameter('d', 0.0);
            p.parse(set_point, varargin{:});
% dev1290            
%             ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/input'], 2);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/setpoint'], set_point);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/demod/timeconstant'], ziBW2TC(obj.dmod_bw, obj.dmod_od) );
%             ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/demod/order'], obj.dmod_od);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/demod/harmonic'], 1);
%             ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/output'], 3);
%             ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/outputchannel'], str2double(obj.aux_ch) );
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/center'], 0);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/range'], p.Results.limit_range);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/p'], p.Results.p);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/i'], p.Results.i);
%             ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/d'], p.Results.d);
%             ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/enable'], 0); 

            ziDAQ('setInt',   ['/' obj.device '/pids/' obj.pid_ch '/mode'], 0);
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/input'], 2);
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/inputchannel'], 0);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/setpoint'], set_point);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/demod/timeconstant'], ziBW2TC(obj.dmod_bw, obj.dmod_od) );
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/demod/order'], obj.dmod_od);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/demod/harmonic'], 1);
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/output'], 7);
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/outputchannel'], str2double(obj.aux_ch) );
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/center'], obj.find_control_volt(set_point));
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/limitlower'], -1.0);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/limitupper'], 1.0);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/p'], p.Results.p);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/i'], p.Results.i);
            ziDAQ('setDouble', ['/' obj.device '/pids/' obj.pid_ch '/d'], p.Results.d);
            ziDAQ('setInt',    ['/' obj.device '/pids/' obj.pid_ch '/enable'], 0); 
        end
        
        %% Calibration
        function data = calibration(obj, storagename)
            clear textprogressbar;
            swpr = obj.zi.sweepModule(obj.session);
            
            %reset contrl voltage + aux voltage
            obj.unlock().setOsc_Dmod();
            obj.setContrlVoltage(0.0);
            obj.zi.disableSigOutAdd()...
                  .fireSigOut();

            %sweeper setting
            swpr.setControl('gridnode', 'sigouts/0/offset', ...
                            'start', 0.0, ...
                            'stop', 10.0);
            
            %get data
            swpr.reset();
            swpr.subscribeChannel('0', 'r');
            volt2power = swpr.run();
            swpr.unsubscribeChannel();

            %reset votages
            obj.zi.setAuxOutOffset(obj.aux_ch, 0);
            obj.zi.silenceSigOut();

            calibration.voltCtrl = volt2power.getNodeData('0', 'r').grid;
            calibration.voltPD =  volt2power.getNodeData('0', 'r').value;
            calibration.time = datestr(now,'HH:MM:SS.FFF, mm/dd/yy');
            obj.last_power_calibration.ctrlVolt2pdVolt = calibration;
            data = volt2power;
            
            try
                swpr.addToSession(storagename);
            catch
                warning('Data not add to session.');
            end
        end
        
        function calibration = calibration_voltage2power(obj, pd_volt)
            if nargin == 1
                pd_volt = 0.1:0.1:1.4;
            end
            
            power = zeros(1, length(pd_volt));
            for k = 1:length(pd_volt)
                obj.setTarget( pd_volt(k) );
                power(k) = input(sprintf('set point = %f, measured_power (mW) = ', pd_volt(k) ));
            end
            calibration.volt = pd_volt; calibration.power = power; 
            calibration.time = datestr(now,'HH:MM:SS.FFF, mm/dd/yy');
            obj.last_power_calibration.pdVolt2Power = calibration;
            
            obj.save_calibration();
            obj.fit_calibration(pd_volt, power);
        end
        
        function obj = save_calibration(obj)
            path = [current_file_path(mfilename('fullpath') ) '\'];
            data = obj.last_power_calibration; %#ok<NASGU>
            save([path 'last_calibration'], 'data');
        end
        
        function load_calibration(obj, filename)
            if nargin == 1
                filename = 'last_calibration.mat';
            end
            path = [current_file_path(mfilename('fullpath') ) '\'];
            stored_calibration = load([path filename]);
            obj.last_power_calibration = stored_calibration.data;
        end
        
        function plot_calibration(obj)
            pdVal = 0:0.01:1.1*max(obj.last_power_calibration.pdVolt2Power.volt);
            p1 = obj.last_power_calibration.calibration_fit.p1;
            p2 = obj.last_power_calibration.calibration_fit.p2;
            fit_y = p1*pdVal + p2;
            figure;
            subplot(2, 1, 1);
            plot(obj.last_power_calibration.ctrlVolt2pdVolt.voltCtrl, obj.last_power_calibration.ctrlVolt2pdVolt.voltPD, 'b.-'); 
            title(' PD voltage vs. Control votage');
            xlabel('Control votage (V)'); ylabel('PD voltage (V)');grid on;
            subplot(2, 1, 2);
            plot(obj.last_power_calibration.pdVolt2Power.volt, obj.last_power_calibration.pdVolt2Power.power, 'ro', ...
                 pdVal, fit_y, 'r--'); 
             legend({'data', sprintf('fit: p1 = %f mW/V\np2=%f mW', p1, p2)}, 'location', 'southeast');
             title('Power vs. PD voltage ');
             xlabel('PD voltage (V)'); ylabel('Power (mW)');grid on;
             ylim(minmax(fit_y));
        end
        
        %% Process Characterization
        function data = scope_signal(obj, storagename, varargin)
            clear textprogressbar;
            
            p=inputParser;
            rangeTest = @(x) x>=0 && x <= 15;
            p.addParameter('time_range', 9, rangeTest)
            p.parse(varargin{:});
            
            scope = obj.zi.scopeModule(); 

            scope.reset();

            % Control Panel
            scope.setMode('time');
            scope.setSamplingRate(p.Results.time_range); 
            scope.setFrameLength(1.0);
            scope.enableChannel('1');
            scope.inputSelect('0', 0);

            scope.disableTrigger().setSingleShot();   

            scope.subscribeChannel();
            data=scope.run();
            scope.unsubscribeChannel();        

           
            
            try
                scope.addToSession(storagename);
            catch
                warning('Data not add to session.');
            end
            
    
%             h = scope.hdl;    
%             ziDAQ('setInt', ['/' obj.device '/scopes/0/time'], p.Results.time_range);
%             ziDAQ('subscribe', h, ['/' obj.device '/scopes/0/wave']);
%             ziDAQ('setInt', ['/' obj.device '/scopes/0/enable'], 1);
%             ziDAQ('execute', h);
%             pause(2);
%             data = ziDAQ('read', h);
%             ziDAQ('setInt', ['/' obj.device '/scopes/0/enable'], 0);
%             ziDAQ('finish', h);
%             ziDAQ('unsubscribe', h, '*');
%             
%             % for HF2 devices, the scope data obtained by Matlab API should
%             % be scaled by the input_range, i.e., true scope value = data*input_range
%             data.input_range = ziDAQ('getDouble', ['/' obj.device '/sigins/' obj.sig_ch '/range']);
        end
        
        function data  = plotter_dmod(obj, storagename, varargin)
            clear textprogressbar;
            
            p=inputParser;
            durationTest = @(x) x > 0;
            p.addParameter('duration', 5, durationTest)
            p.parse(varargin{:});
            
            plotter=obj.zi.plotterModule(obj.session);
            
            plotter.reset();
            plotter.subscribeChannel(obj.dmd_ch, 'r');
            data = plotter.run( 'total_duration', p.Results.duration, ...
                                'window_time', p.Results.duration);
            plotter.unsubscribeChannel();
            
            try
                plotter.addToSession(storagename);
            catch
                warning('Data not add to session.');
            end
        end
        
        function data = spectrum_dmod(obj, storagename, varargin)
            clear textprogressbar;
            
            p=inputParser;
            isnonneg = @(x) x > 0;
            p.addParameter('frequency_span', 20e3, isnonneg)
            p.addParameter('grid_cols', 4096, isnonneg)
            p.parse(varargin{:});
            
            spectrum = obj.zi.spectrumModule(obj.session);

            spectrum.setControl('grid_repetitions', 10);
            spectrum.setParam('spectrum/frequencyspan', p.Results.frequency_span);
            spectrum.setParam('grid/cols', p.Results.grid_cols);

            spectrum.reset();
            spectrum.subscribeChannel(obj.dmd_ch, 'sample.R.fft.abs.avg');
            data = spectrum.run();
            spectrum.unsubscribeChannel();
            
            try
                spectrum.addToSession(storagename);
            catch
                warning('Data not add to session.');
            end
        end
        
        function data = pid_response(obj, varargin)
            clear textprogressbar;
            
            p=inputParser;
            setPtTest = @(x) x > 0 && x < 1.5; % max volt = 1.5;
            stepPtTest = @(x) x > 0 && x < 1.0; 
            colTest = @(x) x > 0;
            delayTest = @(x) x < 0;
            
            p.addParameter('pid_setpoint', 1.2, setPtTest);
            p.addParameter('step_voltage', 0.01, stepPtTest);
            p.addParameter('trigger_level', 1.205, setPtTest);
            p.addParameter('grid_cols', 512, colTest);
            p.addParameter('delay', -0.5e-3, delayTest);
            p.parse(varargin{:})
            
            obj.setOsc_Dmod().setTarget(p.Results.pid_setpoint);
            obj.zi.setDataTranferRate(obj.dmd_ch, 100e3)
            
            h = ziDAQ('dataAcquisitionModule');
            ziDAQ('set', h, 'dataAcquisitionModule/triggernode', ['/' obj.device '/demods/' obj.dmd_ch '/sample.R']);
            ziDAQ('set', h, 'dataAcquisitionModule/device', obj.device);
            ziDAQ('set', h, 'dataAcquisitionModule/bandwidth', 0.0000000);
            ziDAQ('set', h, 'dataAcquisitionModule/hysteresis', 0.0000000);
            ziDAQ('set', h, 'dataAcquisitionModule/level', p.Results.trigger_level);
            ziDAQ('set', h, 'dataAcquisitionModule/endless', 0);
            ziDAQ('set', h, 'dataAcquisitionModule/grid/cols', 512);
            ziDAQ('set', h, 'dataAcquisitionModule/delay', -0.0005000);

            % step response
            ziDAQ('subscribe', h, ['/' obj.device '/demods/' obj.dmd_ch '/sample.R.avg']);
            ziDAQ('execute', h);
            tic; triggered = 0;
            while ~ziDAQ('finished', h)
                if toc > 0.1 && ~ triggered
                    obj.setPID(p.Results.pid_setpoint + p.Results.step_voltage);
                    triggered=1;
                    fprintf('Waiting for trigger %0.0f%%\n', ziDAQ('progress', h) * 100);
                end
              pause(0.5);
              data.response = ziDAQ('read', h);
              if triggered
                  fprintf('Triggered %0.0f%%\n', ziDAQ('progress', h) * 100);
              end
            end
            ziDAQ('finish', h);
            ziDAQ('unsubscribe', h, '*');
            
            clockbase = double(ziDAQ('getInt',['/dev3560/clockbase']));
            data.response.clockbase = clockbase;
            
            pid_data = EquipmentControl.ZI.ziData.ziDaqData(data.response);
            obj.session.addMeasurementData('PIDResponse', pid_data);

%             % noise rejection
%             pause(1); obj.setTarget(p.Results.pid_setpoint);
%             ctrl_volt = obj.find_control_volt(p.Results.pid_setpoint);
%             ziDAQ('subscribe', h, ['/' obj.device '/demods/' obj.dmd_ch '/sample.R.avg']);
%             ziDAQ('execute', h);
%             tic; triggered = 0;          
%             while ~ziDAQ('finished', h)
%                 if toc > 0.1 && ~ triggered
% %                     ziDAQ('setDouble', ['/' obj.device '/sigouts/' obj.ctl_ch '/offset'], ctrl_volt * 1.01);
%                     obj.zi(ctrl_volt * 1.01);
%                     triggered=1;
%                     fprintf('waiting for trigger %0.0f%%\n', ziDAQ('progress', h) * 100);
%                 end
%                 pause(0.5);
%                 data.noiseReject = ziDAQ('read', h);
%                 if triggered
%                     fprintf('Triggered %0.0f%%\n', ziDAQ('progress', h) * 100);
%                 end
%             end
%             
%             ziDAQ('finish', h);
%             ziDAQ('unsubscribe', h, '*');
            
            obj.zi.setDataTranferRate(obj.dmd_ch, 10e3)
        end
    end
    
    methods (Access = private)     
        function fit_calibration(obj, pd_volt, power)
            [xData, yData] = prepareCurveData( pd_volt, power );
            ft = fittype( 'poly1' );
            obj.last_power_calibration.calibration_fit = fit( xData, yData, ft );
        end
    end
    
    methods
        performance_check(obj)
        performance_plot(obj)
    end
end

